using System;

namespace PhpDocBlockGenerator.Model.Php
{
    /// <summary>
    /// basic data types of php
    /// </summary>
    public enum PhpDataTypes
    {
        None,
        This,
        Int,
        Float,
        Boolean,
        String,
        Array,
        Null,
        Mixed,
        Void,
        Misc
    }

    /// <summary>
    /// class for a php data type and possible sub types
    /// (e.g. for @return mixed(int, boolean) or @param int)
    /// </summary>
    public class PhpDataType : ICloneable
    {
        /// <summary>
        /// main data type
        /// </summary>
        private PhpDataTypes _type = PhpDataTypes.None;
        public PhpDataTypes Type
        {
            get { return _type; }
            set { _type = value; }
        }

        /// <summary>
        /// if the type is a miscellaneous type
        /// </summary>
        public string MiscType { get; set; }

        /// <summary>
        /// sets the type by a string
        /// </summary>
        public String TypeFromString
        {
            set { SetTypeFromString(value); }
        }

        /// <summary>
        /// empty ctor
        /// </summary>
        public PhpDataType() { }

        /// <summary>
        /// ctor with specific data type (enum) as input
        /// </summary>
        /// <param name="type">data type (enum) to set the object for</param>
        public PhpDataType(PhpDataTypes type)
        {
            Type = type;
        }


        /// <summary>
        /// trys to create a data type object from a given string which represants
        /// a php data type
        /// </summary>
        /// <param name="type">type as string to parse</param>
        /// <returns>created php data type object</returns>
        public static PhpDataType CreateFromString(string type)
        {
            var phpDataType = new PhpDataType();
            phpDataType.SetTypeFromString(type);
            return phpDataType;
        }

        /// <summary>
        /// sets the type by a string
        /// </summary>
        /// <param name="type"></param>
        public void SetTypeFromString(String type)
        {
            bool typeSet = false;
            //if string doesn't exist or is empty return unknown type
            if (String.IsNullOrEmpty(type))
            {
                Type = PhpDataTypes.Mixed;
            }
            else
            {
                //get all php type names
                string[] typeNames = Enum.GetNames(typeof (PhpDataTypes));


                //loop over all possible php types and possibly return found data type
                for (int i = 0; i < typeNames.Length; ++i)
                {
                    if (typeNames[i].ToLower().Equals(type.ToLower()))
                    {
                        Type = (PhpDataTypes) i;
                        typeSet = true;
                        break;
                    }
                }

                if (!typeSet)
                {
                    Type = PhpDataTypes.Misc;
                    MiscType = type;
                }
            }
        }


        /// <summary>
        /// copies the object
        /// </summary>
        /// <returns>cloned object</returns>
        public object Clone()
        {
            var temp = new PhpDataType();
            temp.SetTypeFromString(ToString());

            return temp;
        }

        /// <summary>
        /// checks whether the current object is equal to another object of same type
        /// </summary>
        /// <param name="other">other datatype object to check for</param>
        /// <returns>true in case of equality, false otherwise</returns>
        public bool Equals(PhpDataType other)
        {
            //main types are different -> objects aren't equal
            if (!Type.ToString().Equals(other.Type.ToString()))
                return false;

            //main and sub types are the same and in the same order -> objects are equal
            return true;
        }

        /// <summary>
        /// checks whether a given data type (enum) is equal to the objects main data type
        /// </summary>
        /// <param name="type">data type (enum) to check for</param>
        /// <returns></returns>
        public bool Equals(PhpDataTypes type)
        {
            if (Type == type)
                return true;
            return false;
        }

        /// <summary>
        /// merges the data type with another data type (in case of mixed only)
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public PhpDataType Merge(PhpDataType other)
        {
            //if type isn't mixed of one of the objects then return copy of this object or a new
            //object with type none in case of one of the types is none (none is binding stronger)
            if (!Type.Equals(PhpDataTypes.Mixed) || !other.Type.Equals(PhpDataTypes.Mixed))
            {
                if (Type.Equals(PhpDataTypes.None) || other.Type.Equals(PhpDataTypes.None))
                    return new PhpDataType();
                return Clone() as PhpDataType;
            }

            //create new temp with mixed as type and extracted subtypes
            var temp = new PhpDataType {Type = PhpDataTypes.Mixed};

            return temp;
        }

        /// <summary>
        /// converts the main data type to a string
        /// </summary>
        /// <returns>resulting string</returns>
        public override string ToString()
        {
            if (Type == PhpDataTypes.Misc)
                return MiscType;
            return Type.ToString().ToLower();
        }
    }
}
